package org.openrisk.domain;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "username" }))
public class User extends BasePerson {

	private static final long serialVersionUID = 6715029386366709202L;

	private static final String PASSWORD_MESSAGEDIGEST_ALGORITHM = "SHA-512";
	private static final String PASSWORD_CHARACTER_ENCODING = "UTF-8";

	public static final User SYSTEM = new User("system");
	public static final User ADMIN = new User("admin", digestPassword("admin"),
			UserRole.ADMIN);

	@Column(nullable = false, length = 16)
	private String username;

	@Column(length = 64) // 512 bits = 64 bytes <-- http://en.wikipedia.org/wiki/Secure_Hash_Algorithm
	private String passwordEncoded = null;

	@Column(length = 1)
	// Use JPA alternative, if any...
	@org.hibernate.annotations.Type(type = "org.hibernate.type.YesNoType")
	private Boolean locked = Boolean.TRUE;
	
	@Column(length= 1)
	// Use JPA alternative, if any...
	@org.hibernate.annotations.Type(type = "org.hibernate.type.YesNoType")
	private Boolean changePasswordAfterNextLogin = Boolean.TRUE;
	

	@ManyToMany(targetEntity = org.openrisk.domain.UserRole.class)
	private List<UserRole> userRoles = new ArrayList<UserRole>();

	/**
	 * only user to create system user, therefore private constructor
	 * 
	 * @param username
	 */
	private User(String username) {
		this.username = username;
		this.userRoles.add(UserRole.SYSTEM);
	}

	/**
	 * Create a non system user.
	 * 
	 * @param username
	 * @param passwordEncoded
	 */
	public User(String username, String passwordEncoded, UserRole userRole) {
		this.username = username;
		this.passwordEncoded = passwordEncoded;
		this.userRoles.add(userRole);
		this.locked = Boolean.FALSE;
	}

	public static final String digestPassword(String password) {
		MessageDigest messageDigest;
		try {
			messageDigest = MessageDigest
					.getInstance(PASSWORD_MESSAGEDIGEST_ALGORITHM);
			messageDigest
					.update(password.getBytes(PASSWORD_CHARACTER_ENCODING));
			byte[] digestBytes = messageDigest.digest();
			return new String(digestBytes, PASSWORD_CHARACTER_ENCODING);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(
					"Unable to create instance of MessageDigest with Algorithm "
							+ PASSWORD_MESSAGEDIGEST_ALGORITHM, e);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("Unable to use character encoding "
					+ PASSWORD_CHARACTER_ENCODING, e);
		}
	}

	public final boolean matchPassword(String enteredPassword) {
		return passwordEncoded.equals(digestPassword(enteredPassword));
	}

	/**
	 * @return the username
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * @param username
	 *            the username to set
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * @return the passwordEncoded
	 */
	public String getPasswordEncoded() {
		return passwordEncoded;
	}

	/**
	 * @param passwordEncoded
	 *            the passwordEncoded to set
	 */
	public void setPasswordEncoded(String passwordEncoded) {
		this.passwordEncoded = passwordEncoded;
	}

	/**
	 * @return the locked
	 */
	public Boolean getLocked() {
		return locked;
	}

	/**
	 * @param locked
	 *            the locked to set
	 */
	public void setLocked(Boolean locked) {
		this.locked = locked;
	}

	/**
	 * @param changePasswordAfterNextLogin the changePasswordAfterNextLogin to set
	 */
	public void setChangePasswordAfterNextLogin(
			Boolean changePasswordAfterNextLogin) {
		this.changePasswordAfterNextLogin = changePasswordAfterNextLogin;
	}

	/**
	 * @return the changePasswordAfterNextLogin
	 */
	public Boolean getChangePasswordAfterNextLogin() {
		return changePasswordAfterNextLogin;
	}

	/**
	 * @return the userRoles
	 */
	public List<UserRole> getUserRoles() {
		return userRoles;
	}

	/**
	 * @param userRole
	 *            the userRole to add
	 */
	public void addUserRole(UserRole userRole) {
		this.userRoles.add(userRole);
	}

	/**
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "User [username=" + username + ", passwordEncoded="
				+ passwordEncoded + ", locked=" + locked
				+ ", changePasswordAfterNextLogin="
				+ changePasswordAfterNextLogin + ", userRoles=" + userRoles
				+ "] --> " + super.toString();
	}


}
